package com.gitee.loyo.act

import com.gitee.loyo.Helper
import com.gitee.loyo.KSQL
import com.gitee.loyo.act.remote.RemoteTransferorFactory
import com.gitee.loyo.interfaces.TypeReference
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Proxy
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.isSubclassOf

open class Record<M: Model<M, R>, R: Record<M, R>>(
    @Transient private val ref: RecordRef<M, R>,
    @Transient protected val transferorFactory: TransferorFactory = JDBCTransferorFactory,
){
    @delegate: Transient internal val modelKClass: KClass<M> by lazy {
        ((javaClass.superclass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<M>).kotlin
    }
    @delegate: Transient internal val model: M by lazy {
        getModelCompanion().createModel()
    }

    init {
        ref.ref = this as R
    }

    fun getModelCompanion(): ModelCompanion<M, R>{
        return modelKClass.companionObjectInstance as ModelCompanion<M, R>
    }

    open fun putPrimaryKeyValue(id: Any){
        val fieldColumn = model.getPrimaryKey()
        KSQL.reflection.setFieldValue(this, id, listOf(fieldColumn.fieldName))
    }

    open fun getPrimaryKeyValue(): Any?{
        val column = model.getPrimaryKey()
        return KSQL.reflection.getFieldValue(this, column.fieldName)
    }

    open fun getPrimaryKeyValueMap(): Map<String, Any?>{
        return model.getPrimaryKeys().associate {
            it.fieldName to KSQL.reflection.getFieldValue(this, it.fieldName)
        }
    }

    open fun getTableName(): String {
        return model.getTableNameWithAlias()
    }

    open fun getPrimaryKeyName(): String?{
        return model.getPrimaryKey().columnName
    }

    open fun autoIncrementID(): Boolean{
        return model.getPrimaryKey()?.primaryKey?.autoIncrement?:false
    }

    /**
     * selectByID and populate
     */
    open fun reload(){
        val record = getModelCompanion().findOne{
            val idMap = getPrimaryKeyValueMap()
            getPrimaryKeys().map {
                it eq (idMap[it.fieldName]?:throw NullPointerException("主键${it.fieldName}为空"))
            }
        } as R
        KSQL.reflection.overrideProperties(record, this)
    }

    /**
     * selectById For Update
     */
    open fun lockForUpdate(){
        getModelCompanion().findForUpdate {
            val idMap = getPrimaryKeyValueMap()
            getPrimaryKeys().forEach {
                it eq (idMap[it.fieldName]?:throw NullPointerException("主键${it.fieldName}为空"))
            }
        }
    }

    /**
     * updateByCondition
     */
    open fun save(fn: (M.() -> Unit)? = null): Boolean{
        return getModelCompanion().update(this as R, fn)
    }

    /**
     * updateSelectiveByCondition
     */
    open fun saveAttr(vararg props: KMutableProperty0<out Any?>, fn: (M.() -> Unit)? = null): Boolean{
        if(props.isEmpty())
           throw UnsupportedOperationException("没有发现属性")

        val keyValueMap = getPrimaryKeyValueMap().ifEmpty { null }?:throw UnsupportedOperationException("未发现${modelKClass.qualifiedName}的主键")
        return getModelCompanion().update{
            set(*props.map { getColumn(it.name)!! to it.get() }.toTypedArray())
            where {
                keyValueMap.forEach { (k, v) ->
                    getColumn(k)!! eq v
                }
                fn?.invoke(this)
            }
        }
    }

    /**
     * deleteByID
     */
    open fun remove(): Boolean{
        return getModelCompanion().remove{
            val idMap = getPrimaryKeyValueMap()
            if(this.primaryKeys.isNullOrEmpty())
                throw NullPointerException("未发现主键")
            this.primaryKeys!!.forEach{
                 it eq idMap[it.fieldName]
            }

        } == 1
    }

    internal fun getTransferorFactory() = transferorFactory

    protected inline fun <reified T> rel(): T{
        val recordType: KClass<*> = if(T::class == List::class){
            object : TypeReference<T>(){}.listType()
        }else{
            T::class
        }
        if(recordType.isSubclassOf(Record::class)){
            val record = KSQL.reflection.newDataInstance(recordType, emptyMap()) as Record<*, *>
            val modelCompanion = record.getModelCompanion()
            return if(T::class == List::class){
                manyFrom(modelCompanion) as T
            }else{
                oneFrom(modelCompanion) as T
            }
        }
        throw UnsupportedOperationException("不支持的类型${recordType.qualifiedName}")
    }

    protected fun <N: Model<N, S>, S: Record<N, S>> oneFrom(joinModelCompanion: ModelCompanion<N, S>): S?{
        val primaryKeyMap = getPrimaryKeyValueMap()
        return joinModelCompanion.findOne{
            inner join getModelCompanion() on model.getPrimaryKey() and {
                primaryKeys!!.map { it eq primaryKeyMap[it.fieldName] }.reduce{ a, b -> a and b }
            }
        }
    }

    protected fun <N: Model<N, S>, S: Record<N, S>> manyFrom(joinModelCompanion: ModelCompanion<N, S>): List<S>{
        val primaryKeyMap = getPrimaryKeyValueMap()
        return joinModelCompanion.find{
            inner join getModelCompanion() on model.getPrimaryKey() and {
                primaryKeys!!.map { it eq primaryKeyMap[it.fieldName] }.reduce{ a, b -> a and b }
            }
        }
    }
}

open class RemoteRecord<M: Model<M, R>, R: Record<M, R>>(ref: RecordRef<M, R>): Record<M, R>(ref, RemoteTransferorFactory)

class RecordRef<M: Model<M, R>, R: Record<M, R>>{
    lateinit var ref: R

    inline fun <reified T> dao(): T{
        return Proxy.newProxyInstance(
            KSQL::class.java.classLoader,
            arrayOf<Class<*>>(T::class.java)
        ){ _, method, args ->
            val argsMap = Helper.buildArgsMap(ref, method, args?.toList())
            ref.getModelCompanion().getTransferorFactory().invoke(ref::class.java, method, argsMap)
        } as T
    }

}

inline fun <T: Record<*, T>> Collection<T>.add(){
    Helper.insertModelBatch(this)
}

inline fun <T: Record<*, T>> Collection<T>.save(){
    Helper.updateModelBatch(this)
}

inline fun <T: Record<*, T>> Collection<T>.delete(){
    Helper.deleteModelBatch(this)
}
